Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple worlds/pipelines #328

Closed
wants to merge 114 commits into from

Conversation

AnthonyTornetta
Copy link
Contributor

@AnthonyTornetta AnthonyTornetta commented Feb 14, 2023

This closes #221 by allowing multiple worlds to be used in parallel.

Reasoning

Currently, bevy_rapier restricts its usage to a single world per game, which is limiting in many situations, despite rapier itself having no restriction on the amount of worlds it supports. This PR changes RapierContext to instead store a list of RapierWorlds which hold what RapierContext used to hold. Each world is addressable be a unique WorldId, which can be attached to any entity via the PhysicsWorld component. The physics world simply stores the world's id.

Goal

The goal of this PR was to add support for any number of worlds to bevy_rapier with minimal breaking changes to projects already using this that are content with only one world. The only changes that will be made to such projects are as follows:

  • raycast calls will now need the DEFAULT_WORLD_ID provided as the first argument and you will need to call .expect("The default world should always exist") before using their result.
  • If you provided custom arguments to the RapierContext, you will instead need to do:
RapierContext::new(RapierWorld {
  your custom stuff,
  ..Default::default()
})
  • Similarly, to access those items you now have to do context.(get_world/get_world_mut)(DEFAULT_WORLD_ID).expect("Default world should exist").field_you_wanted
  • Some other things may need to be adjusted to first get the world, but these were the main ones that would affect the most projects.

Example

I have provided a 3d example of the multiple worlds in action in multi_world3.rs.
image

Each of those platform/block pairs is in its own physics world, where they do not interact with each other.

A GIF of a cube changing its world every 3 seconds and moving between platforms in different worlds found in change_world3.rs:
change_world3

PhysicsWorld

This is a component that may be attached to any entity where you want to specify its world. If this component is omitted, the entity will be added to the DEFAULT_WORLD_ID world, which is present in every game. If this component is attached, the entity will be added to the specified world. If the component is modified, the entity should switch the world that it is in to reflect the id it was changed to.

To add or remove a world, just use RapierContext::add_world and RapierContext::remove_world respectively.

@AnthonyTornetta AnthonyTornetta changed the title Multiple worlds Multiple worlds/pipelines Feb 22, 2023
@AnthonyTornetta
Copy link
Contributor Author

This has been updated to bevy 0.10

@AnthonyTornetta
Copy link
Contributor Author

I've made some updates while updating this branch to not have any conflicts, two of which will change the public API:

  • WorldId ID to events (as mentioned above):
    • The WorldId is now included in the CollisionEvent enum. This will cause the public API to change once more, but since it's pretty easy to ignore this field I don't think it's a huge problem.
  • Changed where gravity is defined
    • The integration rework proved problematic for this PR, since the RapierContext resource has been restructured. One of the things that I had to move to make understandable was the gravity field. This is now stored in each world, rather than in the RapierContext. As such, you'll have to do either RapierWorld::set_gravity or RapierWorld::with_gravity, the latter being a builder-like method that returns self.

@Vrixyz
Copy link
Contributor

Vrixyz commented Jun 24, 2024

Thanks again for your ongoing discussion on this topic !

For information, I'm starting to study bevy_rapier API in order to offer a few improvements, mainly RapierContext usage, but I'd love to support your use-case with those API improvements.

The exact details of these improvements are still being worked on, and I'd like to familiarize myself with the design implications and understand in practice the changes necessary for supporting multiple worlds, so I'll be exploring on my own branch from master.

For multi-world feature, I will probably transition RapierContext to be a Component, and explore SystemParams to offer good ergonomics for single world users.
In parallel, I will study bevy_xpbd to compare approaches.

I think this particular Pull Request is unlikely to be merged as-is, but thanks again for the work which will prove handy to compare approaches, and the discussions to find a good path forward !
I'm only stating my own feeling, and to manage expectations, I look forward to further discussions on this not trivial feature 😄.

Comment on lines -58 to +62
pub collision_events: RwLock<EventWriter<'a, CollisionEvent>>,
pub contact_force_events: RwLock<EventWriter<'a, ContactForceEvent>>,
pub collision_events: &'a mut RwLock<Vec<CollisionEvent>>,
pub contact_force_events: &'a mut RwLock<Vec<ContactForceEvent>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good change in my opinion 👍 , it helps with support for multi world and we don't need a whole EventWriter there anyway.

colliders
.get(handle)
.map(|co| Entity::from_bits(co.user_data as u64))
.or_else(|| self.deleted_colliders.get(&handle).copied())
.expect("Internal error: entity not found for collision event.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine that's a good change to allow users to come back from errors, but if we're returning None, isn't that a sign we did a system ordering mistake ?

@AnthonyTornetta
Copy link
Contributor Author

I agree that moving forward with the "componentization" of RapierContext is a good choice. Given that is the direction you're going, I agree that merging these changes in would be pointless, given that many would have to be redone anyway. I would be happy to help out with this architectural shift if you would like any, and if you have questions you want to ask me outside of github, I'm in the dimforge discord server and my username is Cornchip.

I will continue to keep this branch updated for now, since I am using it in my own project. Thanks for taking interest in supporting multiple worlds! I'm sure a lot of people (besides me) will appreciate it.

events.is_some(),
hooks,
time,
sim_to_render_time,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure sharing SimulationToRenderTime between all worlds will still yield useful results. I think we'd want it to be specific to each world.

Vrixyz added a commit to Vrixyz/bevy_rapier that referenced this pull request Jun 26, 2024
example from dimforge#328

Co-authored-by: Anthony Tornetta
<25857049+AnthonyTornetta@users.noreply.github.com>
Vrixyz added a commit to Vrixyz/bevy_rapier that referenced this pull request Jun 26, 2024
example from dimforge#328

Co-authored-by: Anthony Tornetta
<25857049+AnthonyTornetta@users.noreply.github.com>
Vrixyz added a commit to Vrixyz/bevy_rapier that referenced this pull request Jun 26, 2024
example from dimforge#328

Co-authored-by: Anthony Tornetta <25857049+AnthonyTornetta@users.noreply.github.com>
@@ -0,0 +1,111 @@
//! Systems responsible for interfacing our Bevy components with the Rapier physics engine.
Copy link
Contributor

@Vrixyz Vrixyz Jun 27, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for putting those in a separate file 👍 The comment seems a bit off but I'll look into the rest of the code for my own multi-world support attempt :)

/// assuming the child is hit by the full velocity of the parent instead of `parent vel - child vel`.
///
/// This will not change the bevy component's velocity.
pub fn sync_vel(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I assume this is to address #383

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This could be stripped out of this PR for sure, it is not at all necessary for the worlds changes.

///
/// If this fails to happen, weirdness will ensue.
pub fn on_add_entity_with_parent(
q_add_entity_without_parent: Query<(Entity, &Parent), Changed<Parent>>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I imagine we should filter for any handles or existing PhisicsWorld, this query risks iterating through a lot of entities (probably especially UI)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I absolutely agree, I believe when I made that I was just worried about missing any child hierarchy changes. IE: If this entity's children were in the physics simulation, I would want to change its children to reflect the new world they should be a part of. I don't think this is an especially common usecase, I just didn't want any unexpected functionality to be present.

Comment on lines -226 to +229
.before(TransformSystem::TransformPropagate),
.before(TransformSystem::TransformPropagate)
.after(systems::sync_removals),
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

interesting change, do you remember the rationale ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I remember correctly, I did that to prevent any of the init_* systems from running before sync_removals had finished. I believe I was running into inconsistencies in that regard without this being set, causing stuff to be removed after it had been re-added to the proper world.

@AnthonyTornetta
Copy link
Contributor Author

Closing in favor of #545

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-Integration very bevy specific C-Enhancement New feature or request D-Difficult Needs strong technical background, domain knowledge, or impacts are high, needs testing... P-High arbitrary important item
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Feature Request: Multiple pipelines?
3 participants